package application

import (
	"fmt"
	"forum/internal/common"
	"forum/internal/common/config"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"github.com/redis/go-redis/v9"
	"strings"
	"time"
)

type JwtService interface {
	SetLoginJwtToken(ctx *gin.Context, id int64) error
	SetShortToken(ctx *gin.Context, id int64) error
	SetRefreshToken(ctx *gin.Context, id int64) error
	ExtractToken(ctx *gin.Context) string
	RefreshToken(ctx *gin.Context) error
	ClearToken(ctx *gin.Context) error
}

type JwtServiceImp struct {
	jwtSignMethod jwt.SigningMethod
	rcExpiration  time.Duration
	cache         redis.Cmdable
}

func NewJwtServiceImp() *JwtServiceImp {
	return &JwtServiceImp{
		jwtSignMethod: jwt.SigningMethodHS512,
		rcExpiration:  time.Hour * 24 * 7}
}

func (j *JwtServiceImp) SetLoginJwtToken(ctx *gin.Context, id int64) error {
	err := j.SetRefreshToken(ctx, id)
	if err != nil {
		return err
	}
	err = j.SetShortToken(ctx, id)
	return err
}

func (j *JwtServiceImp) SetShortToken(ctx *gin.Context, id int64) error {
	userClaim := &UserClaim{
		Id:        id,
		UserAgent: ctx.GetHeader("User-Agent"),
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 30)),
		},
	}
	token := jwt.NewWithClaims(j.jwtSignMethod, userClaim)
	signedClaims, err := token.SignedString(config.ShortJwtTokenKey)
	if err != nil {
		return err
	}
	ctx.Header("jwt-short-token", signedClaims)
	return nil
}

func (j *JwtServiceImp) SetRefreshToken(ctx *gin.Context, id int64) error {
	refreshClaim := &RefreshClaim{
		Id: id,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.rcExpiration)),
		},
	}
	token := jwt.NewWithClaims(j.jwtSignMethod, refreshClaim)
	signToken, err := token.SignedString(config.RefreshTokenKey)
	if err != nil {
		return err
	}
	ctx.Header("jwt-refresh-token", signToken)
	return err
}

func (j *JwtServiceImp) ExtractToken(ctx *gin.Context) string {
	tokenHeader := ctx.GetHeader("Authorization")
	if tokenHeader == "" {
		return ""
	}
	tokens := strings.Split(tokenHeader, " ")
	if len(tokens) != 2 {
		return ""
	}
	return tokens[1]
}

func (j *JwtServiceImp) RefreshToken(ctx *gin.Context) error {
	tokenString := j.ExtractToken(ctx)
	var claim RefreshClaim
	token, err := jwt.ParseWithClaims(tokenString, &claim, func(token *jwt.Token) (interface{}, error) {
		return config.RefreshTokenKey, nil
	})
	if err != nil {
		return err
	}
	//没登录
	if token == nil || !token.Valid {
		return common.ErrRefreshToken
	}
	ctx.Set("claim", claim)
	return err
}

func (j *JwtServiceImp) ClearToken(ctx *gin.Context) error {
	ctx.Header("jwt-short-token", "")
	ctx.Header("jwt-refresh-token", "")
	uc := ctx.MustGet("user").(UserClaim)
	return j.cache.Set(ctx,
		fmt.Sprintf("users:ssid:%d", uc.Id),
		"", j.rcExpiration).Err()
}

type UserClaim struct {
	Id int64
	jwt.RegisteredClaims
	UserAgent string
}

type RefreshClaim struct {
	Id int64
	jwt.RegisteredClaims
}
